package main.prodcons;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * ProducerConsumer
 * <p/>
 * Producer and consumer tasks in a desktop search application
 *
 * @author Brian Goetz and Tim Peierls
 */
public class ProducerConsumer {
	
	
	public static void main(String[] args) throws InterruptedException {
		File[] roots = { 
//				new File("C:\\db.0002"),
//				new File("C:\\DRIVERS"),
//				new File("C:\\eclipse"),
//				new File("C:\\Intel"),
//				new File("C:\\Program Files"),
//				new File("C:\\Program Files (x86)"),
				new File("C:\\")};
		startIndexing(roots);
	}
	
    static class FileCrawler implements Runnable {
        private final List<LinkedBlockingDeque<File>> queuesList;
        private final FileFilter fileFilter;
        private final File root;
        private final CountDownLatch latch;

        public FileCrawler(List<LinkedBlockingDeque<File>> queuesList,
                           final FileFilter fileFilter,
                           File root,
                           CountDownLatch latch) {
            this.queuesList = queuesList;
            this.root = root;
            this.fileFilter = new FileFilter() {
            	@Override
                public boolean accept(File f) {
                    return f.isDirectory() || fileFilter.accept(f);
                }
            };
            this.latch = latch;
        }

        private boolean alreadyIndexed(File f) {
            return false;
        }

        @Override
        public void run() {
            try {
                crawl(root);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            latch.countDown();
        }

        private void crawl(File root) throws InterruptedException {
            File[] entries = root.listFiles(fileFilter);
            if (entries != null) {
                for (File entry : entries)
                    if (entry.isDirectory())
                        crawl(entry);
                    else if (!alreadyIndexed(entry)){
                    	queuesList.get(getQueueIndex(entry)).put(entry);
                    }
            }
        }
        
        private int getQueueIndex(File file){
        	return  Math.abs(file.hashCode()) % (queuesList.size());
        }
    }

    static class Indexer implements Runnable {
        private final List<LinkedBlockingDeque<File>> queuesList;
        private final int index;
        private final CountDownLatch latch;
        
        private int totalCount;
        private int selfCount;
        private int friendCount;

        public Indexer(List<LinkedBlockingDeque<File>> queuesList, int index, CountDownLatch latch) {
            this.queuesList = queuesList;
            this.index = index;
            this.latch = latch;
        }

        @Override
        public void run() {
                while (true){
               	
                	BlockingDeque<File> myQueue= queuesList.get(index);
                	
                	while(!myQueue.isEmpty()){
                		File file = myQueue.poll();
                		if(file!=null){
                			indexFile(file);
                			selfCount++;
               			}
                	}
                	
                	if(Thread.currentThread().isInterrupted()){
                		System.out.println("Interrupted! : "+index+" : "+totalCount+" : "+selfCount+" : "+friendCount);
                		latch.countDown();
                		return;
                	}
                	
                	for(BlockingDeque<File> queue : queuesList){
                		if(queue == myQueue) continue;
                		if(processQueue(queue)){
                			break;
                		}
                	}
                }
        }

        private boolean processQueue(BlockingDeque<File> queue) {
        	File file = queue.pollLast();
        	if(file!=null)
        	{
        		indexFile(file);
        		friendCount++;
        	}        	
        	return file == null;
        }
        
        public void indexFile(File file) {
            // Index the file...
//        	System.out.println(index + " : " +file.getAbsolutePath());
        	totalCount++;
        };
    }

    private static final int BOUND = 100;
    private static final int N_CONSUMERS = 2;// Runtime.getRuntime().availableProcessors();

    public static void startIndexing(File[] roots) throws InterruptedException {
    	
    	
    	
        ArrayList<LinkedBlockingDeque<File>> queuesList = new ArrayList<LinkedBlockingDeque<File>>(N_CONSUMERS);
        
        ArrayList<Thread> indexerThreads = new ArrayList<>();
        
        ArrayList<Thread> crawlerThreads = new ArrayList<>();
        
        for(int i =0; i < N_CONSUMERS; ++i){
        	queuesList.add(new LinkedBlockingDeque<File>(BOUND));
        }
        
        CountDownLatch crawlersLatch = new CountDownLatch(roots.length);
        CountDownLatch indexersLatch = new CountDownLatch(N_CONSUMERS);
        
        
        FileFilter filter = new FileFilter() {
        	@Override
            public boolean accept(File file) {
                return true;
            }
        };

        
        long time  = System.currentTimeMillis();
        
        for (File root : roots){
        	Thread thread = new Thread(new FileCrawler(queuesList, filter, root, crawlersLatch));
        	crawlerThreads.add(thread);
        	thread.start();
        }

        for (int i = 0; i < N_CONSUMERS; i++){
        	Thread thread = new Thread(new Indexer(queuesList, i, indexersLatch));
        	indexerThreads.add(thread);
        	thread.start();
        }
        
        
        
        crawlersLatch.await();
        
        for(Thread thread : indexerThreads){
        	thread.interrupt();
        }
        
        indexersLatch.await();
        
        System.out.println("Time: "+(System.currentTimeMillis() - time));
    }
    
    
}